/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2010 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author: David Soria Parra <david.soria_parra@mayflower.de>           |
  +----------------------------------------------------------------------+
*/

/* $Id:  $ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_mysqlnd_uh.h"
#include "mysqlnd_uh_classes.h"
#include "mysqlnd_uh.h"

/* mysqli zend class handler */
// #include "../ext/mysqli/php_mysqli_structs.h"
#include "mysqli_comp_structs.h"
zend_class_entry *mysqli_link_class_entry;

/* resources */
int le_mysqlnd_uh_mysqlnd;
int le_mysqlnd_uh_mysqlnd_res;
int le_mysqlnd_uh_mysqlnd_stmt;

/*
  MySQLnd Plugin related
*/
unsigned int mysqlnd_uh_plugin_id;

/*
  mysqlnd function tables for connections. See below how to
  set and modify them to make them call your mysqlnd hooks.
  
  (You would probably declare both of them static, however,
  I need one of them in mysqlnd_uh_classes.c)
*/
static struct st_mysqlnd_conn_methods *my_mysqlnd_conn_methods;
struct st_mysqlnd_conn_methods org_mysqlnd_conn_methods;

/* {{{ */
/*
	mysqlnd plugins can associate arbitrary data pointer with mysqlnd handlers for
	mysqlnd connections, statements, result sets, protocol and net. All of the
	mysqlnd_plugin_get_plugin_<name>_data(...) functions return **void which
	you are supposed to assign some useful memory to. Typically, you will
	want to put a struct here. Make sure to allocate persistent memory, if
	the mysqlnd connection is a persistent one. See below how to free
	the memory at the end of the lifetime of a mysqlnd connection.
*/
static MYSQLND_UH_CONN_DATA** mysqlnd_uh_get_plugin_connection_data(const MYSQLND *conn, zend_bool init_resource TSRMLS_DC) {
	MYSQLND_UH_CONN_DATA **conn_data;

	conn_data = (MYSQLND_UH_CONN_DATA **) mysqlnd_plugin_get_plugin_connection_data(conn, mysqlnd_uh_plugin_id);
	if (conn_data && *conn_data) {
		/* plugin connection data allocated and not set to NULL */
		if (init_resource && !(*conn_data)->conn_resource) {
			/* A bit of a hack - we need to pass the mysqlnd connection to MySQLndUhConnection class */
			zval *conn_resource;
			MAKE_STD_ZVAL(conn_resource);
			ZEND_REGISTER_RESOURCE(conn_resource, (void *)conn, le_mysqlnd_uh_mysqlnd);
			(*conn_data)->conn_resource = conn_resource;
		}
		
	} else {
		/* no data set - allocate memory and initialize it */
		*conn_data = (MYSQLND_UH_CONN_DATA *)mnd_pecalloc(1, sizeof(MYSQLND_UH_CONN_DATA), conn->persistent);
		if (init_resource) {
			zval *conn_resource;
			MAKE_STD_ZVAL(conn_resource);
			ZEND_REGISTER_RESOURCE(conn_resource, (void *)conn, le_mysqlnd_uh_mysqlnd);
			(*conn_data)->conn_resource = conn_resource;
		}
	}

	return conn_data;
}
/* }}} */

#define RETVAL_CHECK_TYPE(retval, method_name, expect_type, type_name) \
	if (retval && MYSQLND_UH_G(report_wrong_types) && (Z_TYPE_P(retval) != expect_type)) { \
		php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " The method " MYSQLND_UH_CLASS_CONNECTION_NAME "::" method_name "() did not return a " type_name " value as it should"); \
	}

#define RETVAL_BOOL_CHECK_TYPE(retval, method_name) \
		RETVAL_CHECK_TYPE(retval, method_name, IS_BOOL, "boolean")

#define RETVAL_STRING_CHECK_TYPE(retval, method_name) \
		RETVAL_CHECK_TYPE(retval, method_name, IS_STRING, "string")

#define RETVAL_NUMBER_CHECK_TYPE(retval, method_name, type_name) \
	if (retval && MYSQLND_UH_G(report_wrong_types) && \
			((Z_TYPE_P(retval) != IS_LONG) && (Z_TYPE_P(retval) != IS_DOUBLE) && (Z_TYPE_P(retval) != IS_STRING))) { \
		php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " The method " MYSQLND_UH_CLASS_CONNECTION_NAME "::" method_name "() did not return a numeric value as it should (C type used by mysqlnd: " #type_name ")"); \
	}

#define RETVAL_BOOL_TO_FUNC_STATUS(retval, ret) \
	if (retval) { \
		ret = (Z_BVAL_P(retval)) ? PASS : FAIL; \
		zval_ptr_dtor(&retval); \
	}

#define REVAL_INTERNAL_RESOURCE(retval, resource, resource_type, resource_name, le_resource, bail_if_no_resource) \
	if (retval) { \
		if (Z_TYPE_P(retval) == IS_RESOURCE) { \
			ZEND_FETCH_RESOURCE_NO_RETURN(resource, resource_type, &retval, -1, (bail_if_no_resource) ? resource_name : NULL, le_resource); \
			if (resource) { \
				zend_hash_index_del(&EG(regular_list), Z_RESVAL_P(retval)); \
			} \
		} \
		zval_ptr_dtor(&retval); \
	} else { \
		resource = NULL; \
	} \
	if (bail_if_no_resource && !resource) \
		php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " You either have not called the appropriate method from the " \
			MYSQLND_UH_CLASS_CONNECTION_NAME \
			" class or you are not returning the return value you got from it. Wrong results, errors and even crashes may happen!");

#define ARG_STRING_LEN(name, len) \
	zval *zv_##name; \
	MAKE_STD_ZVAL(zv_##name); \
	ZVAL_STRINGL(zv_##name, name, (len), 1)

#define ARG_STRING(name) \
	ARG_STRING_LEN(name, ((name) ? strlen((name)) : 0))

#define ARG_LONG(name) \
	zval *zv_##name; \
	MAKE_STD_ZVAL(zv_##name); \
	ZVAL_LONG(zv_##name, (long)(name))

#define ARG_BOOL(name) \
	zval *zv_##name; \
	MAKE_STD_ZVAL(zv_##name); \
	ZVAL_BOOL(zv_##name, (name))

#define ARG_CONN_RESOURCE(resource, plugin_data_ptr_ptr) \
	zval *resource; \
	resource = (*plugin_data_ptr_ptr)->conn_resource;

#define ARG_NULL(name) \
	zval *zv_##name; \
	MAKE_STD_ZVAL(zv_##name); \
	ZVAL_NULL(zv_##name)

#define EXTRACT_DATA_AND_USER_OBJ(mysqlnd_conn, plugin_data, user_hook_obj) \
	MYSQLND_UH_CONN_DATA **plugin_data; \
	zval *user_hook_obj = NULL; \
	plugin_data = (MYSQLND_UH_CONN_DATA **) mysqlnd_uh_get_plugin_connection_data((const MYSQLND*)mysqlnd_conn, TRUE TSRMLS_CC); \
	user_hook_obj = (plugin_data && (*plugin_data)->user_obj) ? (*plugin_data)->user_obj : MYSQLND_UH_G(conn_user_obj);

/* {{{ enum_func_status MYSQLND_METHOD(mysqlnd_uh, query) */
/*
	Take this as an example of mysqlnd plugin proxy method.

	It is as simple as it seems!

	Declare a C function with the signature
	that mysqlnd wants and either do something meaningful or
	just call the original mysqlnd C function. You have the
	address of that mysqlnd C function in your org_mysqlnd_conn_methods
	struct.
*/
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, query)(MYSQLND * conn, const char *query, unsigned int query_len TSRMLS_DC)
{
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		/* User has set a connection proxy object */

		/* Prepare zval* parameter for calling user object */
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING(query);
		
		/* Call user method */
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "query", &retval, conn_resource, zv_query);
		zval_ptr_dtor(&zv_query);

		RETVAL_BOOL_CHECK_TYPE(retval, "query");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		/* No user connection proxy object set, do nothing by calling original mysqlnd C function */
		ret = org_mysqlnd_conn_methods.query(conn, query, query_len TSRMLS_CC);
	}

	return ret;
}
/* }}} */

/* {{{ enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, init)(MYSQLND * conn) */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, init)(MYSQLND * conn TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "init", &retval, conn_resource);
		RETVAL_BOOL_CHECK_TYPE(retval, "init");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
		if (MYSQLND_UH_G(report_wrong_types) && !conn->net) {
			php_error_docref(NULL TSRMLS_CC, E_ERROR, MYSQLND_UH_ERROR_PREFIX " The MySQLnd connection handle has not been initialized properly. PHP/MySQLnd will crash. You must always call the init() method from the build-in base class " MYSQLND_UH_CLASS_CONNECTION_NAME);
		}
	} else {
		ret = org_mysqlnd_conn_methods.init(conn TSRMLS_CC);
	}
	return ret;
}
/* }}} */

/* {{{ enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, connect)(MYSQLND *conn, const char *host, const char * user, const char * passwd,
			unsigned int passwd_len, const char * db, unsigned int db_len, unsigned int port, const char * socket, unsigned int mysql_flags) */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, connect)(MYSQLND *conn, const char *host, const char * user,
	const char * passwd, unsigned int passwd_len, const char * db, unsigned int db_len,
	unsigned int port, const char * socket, unsigned int mysql_flags TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING(host);
		ARG_STRING(user);
		/* KLUDGE - passwd_len is always 0 ! mysqlnd issue ?! */
		ARG_STRING_LEN(passwd, (passwd) ? strlen(passwd) : 0);
		ARG_STRING_LEN(db, db_len);
		ARG_LONG(port);
		ARG_STRING(socket);
		ARG_LONG(mysql_flags);

		zval *retval = NULL;
		mysqlnd_uh_call_method_with_8_params(conn_obj, NULL, "connect", &retval, conn_resource,
			zv_host, zv_user, zv_passwd, zv_db, zv_port, zv_socket, zv_mysql_flags);

		zval_ptr_dtor(&zv_host);
		zval_ptr_dtor(&zv_user);
		zval_ptr_dtor(&zv_passwd);
		zval_ptr_dtor(&zv_db);
		zval_ptr_dtor(&zv_port);
		zval_ptr_dtor(&zv_socket);
		zval_ptr_dtor(&zv_mysql_flags);

		RETVAL_BOOL_CHECK_TYPE(retval, "connect");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.connect(conn, host, user, passwd, passwd_len, db, db_len, port, socket, mysql_flags TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
ulong MYSQLND_METHOD(mysqlnd_uh_conn, escape_string)(const MYSQLND * const conn, char *newstr, const char *escapestr, size_t escapestr_len TSRMLS_DC) {
	ulong ret = 0;
	size_t newstr_len = (2 * escapestr_len) - 1;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING_LEN(escapestr, escapestr_len);
		zval *retval = NULL;

		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "escapestring", &retval, conn_resource, zv_escapestr);
		zval_ptr_dtor(&zv_escapestr);
/*		RETVAL_STRING_CHECK_TYPE(retval, "escapeString"); */
		if (retval) {
			if (Z_TYPE_P(retval) == IS_STRING) {
				ret = Z_STRLEN_P(retval);
				if (Z_STRLEN_P(retval) > newstr_len) {
					php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " The method " MYSQLND_UH_CLASS_CONNECTION_NAME
						"::escapeString() must not return a string that is more than 2x longer than the input string. Return value will be trimmed to avoid problems");
					ret = newstr_len;
				}
				if (ret)
					strncpy(newstr, Z_STRVAL_P(retval), ret);
				newstr[ret] = '\0';
			} else {
				if (MYSQLND_UH_G(report_wrong_types))
					php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " The method " MYSQLND_UH_CLASS_CONNECTION_NAME
						"::escapeString() did not return a string value as it should");
				*newstr = '\0';
			}
			zval_ptr_dtor(&retval);
		}

	} else {
		ret = org_mysqlnd_conn_methods.escape_string(conn, newstr, escapestr, escapestr_len TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, set_charset)(MYSQLND * const conn, const char * const charset TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING(charset);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "setcharset", &retval, conn_resource, zv_charset);
		zval_ptr_dtor(&zv_charset);
		RETVAL_BOOL_CHECK_TYPE(retval, "setCharset");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.set_charset(conn, charset TSRMLS_CC);
	}

	return ret;
}
/* }}} */

/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, send_query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING_LEN(query, query_len);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "sendquery", &retval, conn_resource, zv_query);
		zval_ptr_dtor(&zv_query);
		RETVAL_BOOL_CHECK_TYPE(retval, "sendQuery");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.send_query(conn, query, query_len TSRMLS_CC);
	}

	return ret;
}
/* }}} */

#define MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_FUNC_STATUS(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl) \
	enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, mysqlnd_method_name)(conn_arg_decl TSRMLS_DC) { \
		enum_func_status ret = FAIL; \
		\
		EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj); \
		if (conn_obj) {	\
			ARG_CONN_RESOURCE(conn_resource, conn_data); \
			zval *retval = NULL; \
			mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, #userland_method_name, &retval, conn_resource);	\
			RETVAL_BOOL_CHECK_TYPE(retval, #userland_method_name_cc); \
			RETVAL_BOOL_TO_FUNC_STATUS(retval, ret); \
		} else { \
			ret = org_mysqlnd_conn_methods.mysqlnd_method_name(conn TSRMLS_CC); \
		} \
		\
		return ret; \
	}

#define MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_NUMBER(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl, number_type_decl) \
	number_type_decl MYSQLND_METHOD(mysqlnd_uh_conn, mysqlnd_method_name)(conn_arg_decl TSRMLS_DC) { \
		number_type_decl ret = 0; \
		\
		EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj); \
		if (conn_obj) {	\
			ARG_CONN_RESOURCE(conn_resource, conn_data); \
			zval *retval = NULL; \
			mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, #userland_method_name, &retval, conn_resource);	\
			RETVAL_NUMBER_CHECK_TYPE(retval, #userland_method_name_cc, #number_type_decl) \
			if (retval) { \
				ret = (number_type_decl)(Z_LVAL_P(retval)); \
				zval_ptr_dtor(&retval); \
			} \
		} else { \
			ret = org_mysqlnd_conn_methods.mysqlnd_method_name(conn TSRMLS_CC); \
		} \
		\
		return ret; \
	}

#define MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl) \
		MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_NUMBER(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl, unsigned int)

#define MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT64(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl) \
		MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_NUMBER(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl, uint64_t)

#define MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_ULONG(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl) \
		MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_NUMBER(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl, unsigned long)

#define MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_STR(mysqlnd_method_name, userland_method_name, userland_method_name_cc, conn_arg_decl, target, target_size, allow_null) \
	const char * MYSQLND_METHOD(mysqlnd_uh_conn, mysqlnd_method_name)(conn_arg_decl TSRMLS_DC) { \
		const char *ret = NULL; \
		\
		EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj); \
		if (conn_obj) {	\
			ARG_CONN_RESOURCE(conn_resource, conn_data); \
			zval *retval = NULL; \
			mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, #userland_method_name, &retval, conn_resource);	\
			RETVAL_STRING_CHECK_TYPE(retval, #userland_method_name_cc); \
			if (retval) { \
				if (Z_TYPE_P(retval) == IS_STRING) { \
					/* no error - we alloc target_size + 1 */ \
					strncpy(target, Z_STRVAL_P(retval), target_size) ; \
					if (Z_STRLEN_P(retval) > (target_size - 1)) { \
						php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " The method " MYSQLND_UH_CLASS_CONNECTION_NAME "::" \
						#userland_method_name_cc "() should not return a string longer than " #target_size " - 1 characters. " \
							"The return value will be trimmed"); \
						target[target_size - 1] = '\0'; \
					} else { \
						target[Z_STRLEN_P(retval)] = '\0'; \
					} \
					ret = target; \
				} \
				zval_ptr_dtor(&retval); \
			} \
			if (!allow_null && !ret) \
				php_error_docref(NULL TSRMLS_CC, E_ERROR, MYSQLND_UH_ERROR_PREFIX " The method " MYSQLND_UH_CLASS_CONNECTION_NAME "::" \
				#userland_method_name_cc "() has either returned an empty string or nothing at all. An empty string may crash PHP"); \
		} else { \
			ret = org_mysqlnd_conn_methods.mysqlnd_method_name(conn TSRMLS_CC); \
		} \
		return ret; \
	}

MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_FUNC_STATUS(reap_query, reapquery, reapQuery, MYSQLND *conn)

/* {{{ */
MYSQLND_RES * MYSQLND_METHOD(mysqlnd_uh_conn, use_result)(MYSQLND * const conn TSRMLS_DC) {
	MYSQLND_RES* res = NULL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval;
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "useresult", &retval, conn_resource);
		REVAL_INTERNAL_RESOURCE(retval, res, MYSQLND_RES*, MYSQLND_UH_RES_MYSQLND_RESULT_NAME, le_mysqlnd_uh_mysqlnd_res, 0);
	} else {
		res = org_mysqlnd_conn_methods.use_result(conn TSRMLS_CC);
	}

	return res;
}
/* }}} */

/* {{{ */
MYSQLND_RES * MYSQLND_METHOD(mysqlnd_uh_conn, store_result)(MYSQLND * const conn TSRMLS_DC) {
	MYSQLND_RES* res = NULL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval;
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "storeresult", &retval, conn_resource);
		REVAL_INTERNAL_RESOURCE(retval, res, MYSQLND_RES*, MYSQLND_UH_RES_MYSQLND_RESULT_NAME, le_mysqlnd_uh_mysqlnd_res, 0);
	} else {
		res = org_mysqlnd_conn_methods.store_result(conn TSRMLS_CC);
	}

	return res;
}
/* }}} */

MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_FUNC_STATUS(next_result, nextresult, nextResult, MYSQLND *conn)

/* {{{ */
MYSQLND_STMT *	MYSQLND_METHOD(mysqlnd_uh_conn, stmt_init)(MYSQLND * const conn TSRMLS_DC) {
	MYSQLND_STMT* res = NULL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval;
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "stmtinit", &retval, conn_resource);
		REVAL_INTERNAL_RESOURCE(retval, res, MYSQLND_STMT*, MYSQLND_UH_RES_MYSQLND_STMT_NAME, le_mysqlnd_uh_mysqlnd_stmt, MYSQLND_UH_G(report_wrong_types));
	} else {
		res = org_mysqlnd_conn_methods.stmt_init(conn TSRMLS_CC);
	}

	return res;
}
/* }}} */


/* {{{ */
zend_bool MYSQLND_METHOD(mysqlnd_uh_conn, more_results)(const MYSQLND * const conn TSRMLS_DC) {
	zend_bool ret = FALSE;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "moreresults", &retval, conn_resource);
		RETVAL_BOOL_CHECK_TYPE(retval, "moreResults");
		if (retval) {
			ret = Z_BVAL_P(retval);
			zval_ptr_dtor(&retval);
		}
	} else {
		ret = org_mysqlnd_conn_methods.more_results(conn TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, shutdown_server)(MYSQLND * const conn, uint8_t level TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_LONG(level);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "shutdownserver", &retval, conn_resource, zv_level);
		zval_ptr_dtor(&zv_level);
		RETVAL_BOOL_CHECK_TYPE(retval, "shutdownServer");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.shutdown_server(conn, level TSRMLS_CC);
	}

	return ret;
}
/* }}} */

/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, refresh_server)(MYSQLND * const conn, uint8_t options TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_LONG(options);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "refresh_server", &retval, conn_resource, zv_options);
		zval_ptr_dtor(&zv_options);
		RETVAL_BOOL_CHECK_TYPE(retval, "refresh_server");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.refresh_server(conn, options TSRMLS_CC);
	}

	return ret;
}
/* }}} */

MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_FUNC_STATUS(ping, ping, ping, MYSQLND * const conn)

/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, kill_connection)(MYSQLND * const conn, unsigned int pid TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_LONG(pid);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "killconnection", &retval, conn_resource, zv_pid);
		zval_ptr_dtor(&zv_pid);
		RETVAL_BOOL_CHECK_TYPE(retval, "killConnection");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.kill_connection(conn, pid TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, select_db)(MYSQLND * const conn, const char * const db, unsigned int db_len TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING_LEN(db, db_len);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "selectdb", &retval, conn_resource, zv_db);
		zval_ptr_dtor(&zv_db);
		RETVAL_BOOL_CHECK_TYPE(retval, "selectDb");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.select_db(conn, db, db_len TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, change_user)(MYSQLND * const conn, const char * user, const char * passwd, const char * db, zend_bool silent TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING(user);
		ARG_STRING(passwd);
		ARG_STRING(db);
		ARG_BOOL(silent);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_5_params(conn_obj, NULL, "changeuser", &retval, conn_resource, zv_user, zv_passwd, zv_db, zv_silent);
		zval_ptr_dtor(&zv_user);
		zval_ptr_dtor(&zv_passwd);
		zval_ptr_dtor(&zv_db);
		zval_ptr_dtor(&zv_silent);
		RETVAL_BOOL_CHECK_TYPE(retval, "changeUser");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.change_user(conn, user, passwd, db, silent TSRMLS_CC);
	}

	return ret;
}
/* }}} */


MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_FUNC_STATUS(server_dump_debug_information, serverdumpdebuginformation, serverDumpDebugInformation, MYSQLND * const conn)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT(get_error_no, geterrornumber, getErrorNumber, const MYSQLND * const conn)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_STR(get_error_str, geterrorstring, getErrorString, const MYSQLND * const conn, MYSQLND_UH_G(conn_error_str), MYSQLND_UH_ERRMSG_SIZE, 1)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_STR(get_sqlstate, getsqlstate, getSqlstate, const MYSQLND * const conn, MYSQLND_UH_G(conn_sqlstate), MYSQLND_UH_SQLSTATE_LENGTH, 1)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT64(get_thread_id, getthreadid, getThreadId, const MYSQLND * const conn)


/* {{{ */
void MYSQLND_METHOD(mysqlnd_uh_conn, get_statistics)(const MYSQLND * const conn, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC) {
	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval;
		/* TODO - FIXME - TSRMLS_CC ZEND_FILE_LINE_CC ??? */
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "getstatistics", &retval, conn_resource);
		if (retval) {
			/* TODO - macro? */
			if (Z_TYPE_P(retval) == IS_ARRAY) {
				*return_value = *retval;
				zval_copy_ctor(return_value);
			} else {
				if (MYSQLND_UH_G(report_wrong_types))
					php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " The " MYSQLND_UH_CLASS_CONNECTION_NAME "::getStatistics() callback did not return an array as it should");
			}
			zval_ptr_dtor(&retval);
		}
	} else {
		org_mysqlnd_conn_methods.get_statistics(conn, return_value TSRMLS_CC ZEND_FILE_LINE_CC);
	}
}
/* }}} */


MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_ULONG(get_server_version, getserverversion, getServerVersion, const MYSQLND * const conn)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_STR(get_server_information, getserverinformation, getServerInformation, const MYSQLND * const conn, MYSQLND_UH_G(conn_server_info), MYSQLND_UH_SERVER_INFO_LENGTH, 1)


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, get_server_statistics)(MYSQLND *conn, char **message, unsigned int * message_len TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval;
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "getserverstatistics", &retval, conn_resource);
		RETVAL_STRING_CHECK_TYPE(retval, "getServerStatistics");
		if (retval) {
			if (Z_TYPE_P(retval) == IS_STRING) {
				ret = PASS;
				*message_len = (unsigned int)Z_STRLEN_P(retval);
				*message = estrndup(Z_STRVAL_P(retval), *message_len);
			}
			zval_ptr_dtor(&retval);
		}
	} else {
		ret = org_mysqlnd_conn_methods.get_server_statistics(conn, message, message_len TSRMLS_CC);
	}

	return ret;
}
/* }}} */


MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_STR(get_host_information, gethostinformation, getHostInformation, const MYSQLND * const conn, MYSQLND_UH_G(conn_host_info), MYSQLND_UH_HOST_INFO_LENGTH, 1)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT(get_protocol_information, getprotocolinformation, getProtocolInformation, const MYSQLND * const conn)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_STR(get_last_message, getlastmessage, getLastMessage, const MYSQLND * const conn, MYSQLND_UH_G(conn_last_message), MYSQLND_UH_LAST_MESSAGE_LENGTH, 1)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_STR(charset_name, charsetname, charsetName, const MYSQLND * const conn, MYSQLND_UH_G(conn_charset_name), MYSQLND_UH_CHARSET_NAME_LENGTH, 0)


/* {{{ */
MYSQLND_RES * MYSQLND_METHOD(mysqlnd_uh_conn, list_fields)(MYSQLND *conn, const char *table, const char *achtung_wild TSRMLS_DC) {
	MYSQLND_RES* res = NULL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING(table);
		ARG_STRING(achtung_wild);
		zval *retval;
		mysqlnd_uh_call_method_with_3_params(conn_obj, NULL, "listfields", &retval, conn_resource, zv_table, zv_achtung_wild);
		zval_ptr_dtor(&zv_table);
		zval_ptr_dtor(&zv_achtung_wild);
		REVAL_INTERNAL_RESOURCE(retval, res, MYSQLND_RES*, MYSQLND_UH_RES_MYSQLND_RESULT_NAME, le_mysqlnd_uh_mysqlnd_res, 0);
	} else {
		res = org_mysqlnd_conn_methods.list_fields(conn, table, achtung_wild TSRMLS_CC);
	}

	return res;
}
/* }}} */


/* {{{ */
MYSQLND_RES * MYSQLND_METHOD(mysqlnd_uh_conn, list_method)(MYSQLND *conn, const char *query, const char *achtung_wild, char * par1 TSRMLS_DC) {
	MYSQLND_RES* res = NULL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING(query);
		ARG_STRING(achtung_wild);
		ARG_STRING(par1);
		zval *retval;
		mysqlnd_uh_call_method_with_4_params(conn_obj, NULL, "listmethod", &retval, conn_resource, zv_query, zv_achtung_wild, zv_par1);
		zval_ptr_dtor(&zv_query);
		zval_ptr_dtor(&zv_achtung_wild);
		zval_ptr_dtor(&zv_par1);
		REVAL_INTERNAL_RESOURCE(retval, res, MYSQLND_RES*, MYSQLND_UH_RES_MYSQLND_RESULT_NAME, le_mysqlnd_uh_mysqlnd_res, 0);
	} else {
		res = org_mysqlnd_conn_methods.list_method(conn, query, achtung_wild, par1 TSRMLS_CC);
	}

	return res;
}
/* }}} */


MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT64(get_last_insert_id, getlastinsertid, getLastInsertId, const MYSQLND * const conn)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT64(get_affected_rows, getaffectedrows, getAffectedRows, const MYSQLND * const conn)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT(get_warning_count, getwarningcount, getWarningCount, const MYSQLND * const conn)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_UINT(get_field_count, getfieldcount, getFieldCount, const MYSQLND * const conn)


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, set_server_option)(MYSQLND * const conn, enum_mysqlnd_server_option option TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_LONG(option);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "setserveroption", &retval, conn_resource, zv_option);
		zval_ptr_dtor(&zv_option);
		RETVAL_BOOL_CHECK_TYPE(retval, "setSeverOption");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.set_server_option(conn, option TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, set_client_option)(MYSQLND * const conn, enum_mysqlnd_option option, const char * const value TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_LONG(option);

		zval *zv_value;
		MAKE_STD_ZVAL(zv_value);

		switch (option) {
			case MYSQL_OPT_CONNECT_TIMEOUT:
			case MYSQL_OPT_PROTOCOL:
			case MYSQL_OPT_READ_TIMEOUT:
			case MYSQL_OPT_WRITE_TIMEOUT:
			case MYSQLND_OPT_NET_CMD_BUFFER_SIZE:
			case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
				ZVAL_LONG(zv_value, (*(unsigned int*)value));
				break;

			case MYSQL_OPT_LOCAL_INFILE:
				if (value) {
					ZVAL_LONG(zv_value, *(unsigned int*)value);
				} else {
					ZVAL_NULL(zv_value);
				}
				break;

			case MYSQL_OPT_COMPRESS:
			case MYSQL_OPT_NAMED_PIPE:
			case MYSQL_OPT_USE_RESULT:
			case MYSQL_OPT_USE_REMOTE_CONNECTION:
			case MYSQL_OPT_USE_EMBEDDED_CONNECTION:
			case MYSQL_OPT_GUESS_CONNECTION:
				/* value not used */
				ZVAL_NULL(zv_value);
				break;

			case MYSQL_INIT_COMMAND:
			case MYSQL_READ_DEFAULT_FILE:
			case MYSQL_READ_DEFAULT_GROUP:
			case MYSQL_SET_CHARSET_DIR:
			case MYSQL_SET_CHARSET_NAME:
			case MYSQL_SHARED_MEMORY_BASE_NAME:
			case MYSQLND_OPT_SSL_KEY:
			case MYSQLND_OPT_SSL_CERT:
			case MYSQLND_OPT_SSL_CA:
			case MYSQLND_OPT_SSL_CAPATH:
			case MYSQLND_OPT_SSL_CIPHER:
			case MYSQLND_OPT_SSL_PASSPHRASE:
				ZVAL_STRING(zv_value, value, 1);
				break;

			case MYSQL_SECURE_AUTH:
			case MYSQL_REPORT_DATA_TRUNCATION:
			case MYSQL_OPT_RECONNECT:
			case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
#if	PHP_MAJOR_VERSION >= 6
			case MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE:
#endif
#ifdef MYSQLND_STRING_TO_INT_CONVERSION
			case MYSQLND_OPT_INT_AND_FLOAT_NATIVE:
#endif
				/* TODO  - my_bool*, see also mysqli_api.c */
				ZVAL_BOOL(zv_value, *(unsigned int *)value);
				break;

			default:
				php_error_docref(NULL TSRMLS_CC, E_ERROR, MYSQLND_UH_ERROR_PREFIX " Unknown option. Option value is considered to be a string ");
				ZVAL_STRING(zv_value, value, 1);
				break;
		}

		zval *retval = NULL;
		mysqlnd_uh_call_method_with_3_params(conn_obj, NULL, "setclientoption", &retval, conn_resource, zv_option, zv_value);
		zval_ptr_dtor(&zv_option);
		zval_ptr_dtor(&zv_value);
		RETVAL_BOOL_CHECK_TYPE(retval, "setClientOption");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.set_client_option(conn, option, value TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, close)(MYSQLND *conn, enum_connection_close_type close_type TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_LONG(close_type);
		zval *retval = NULL;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "close", &retval, conn_resource, zv_close_type);
		zval_ptr_dtor(&zv_close_type);
		RETVAL_BOOL_CHECK_TYPE(retval, "close");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.close(conn, close_type TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, query_read_result_set_header)(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *stmt_resource;
		MAKE_STD_ZVAL(stmt_resource);
		if (stmt) {
			ZEND_REGISTER_RESOURCE(stmt_resource, (void *)stmt, le_mysqlnd_uh_mysqlnd_stmt);
		} else {
			ZVAL_NULL(stmt_resource);
		}
		zval *retval;
		mysqlnd_uh_call_method_with_2_params(conn_obj, NULL, "queryreadresultsetheader", &retval, conn_resource, stmt_resource);
		if (stmt) {
			zend_hash_index_del(&EG(regular_list), Z_RESVAL_P(stmt_resource));
		}
		zval_ptr_dtor(&stmt_resource);
		RETVAL_BOOL_CHECK_TYPE(retval, "queryReadResultsetHeader");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.query_read_result_set_header(conn, stmt TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, simple_command)(MYSQLND *conn, enum php_mysqlnd_server_command command, const char * const arg, size_t arg_len, enum mysqlnd_packet_type ok_packet, zend_bool silent, zend_bool ignore_upsert_status TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_LONG(command);
		ARG_STRING_LEN(arg, arg_len);
		ARG_LONG(ok_packet)
		ARG_BOOL(silent)
		ARG_BOOL(ignore_upsert_status);
		zval *retval;
		mysqlnd_uh_call_method_with_6_params(conn_obj, NULL, "simplecommand", &retval, conn_resource, zv_command, zv_arg, zv_ok_packet, zv_silent, zv_ignore_upsert_status);
		zval_ptr_dtor(&zv_command);
		zval_ptr_dtor(&zv_arg);
		zval_ptr_dtor(&zv_ok_packet);
		zval_ptr_dtor(&zv_silent);
		zval_ptr_dtor(&zv_ignore_upsert_status);
		RETVAL_BOOL_CHECK_TYPE(retval, "simpleCommand");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.simple_command(conn, command, arg, arg_len, ok_packet, silent, ignore_upsert_status TSRMLS_CC);
	}

	return ret;
}
/* }}} */


/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, simple_command_handle_response)(MYSQLND *conn, enum mysqlnd_packet_type ok_packet, zend_bool silent, enum php_mysqlnd_server_command command, zend_bool ignore_upsert_status TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_LONG(ok_packet);
		ARG_BOOL(silent);
		ARG_LONG(command);
		ARG_BOOL(ignore_upsert_status);
		zval *retval;
		mysqlnd_uh_call_method_with_5_params(conn_obj, NULL, "simplecommandhandleresponse", &retval, conn_resource, zv_ok_packet, zv_silent, zv_command, zv_ignore_upsert_status);
		zval_ptr_dtor(&zv_ok_packet);
		zval_ptr_dtor(&zv_silent);
		zval_ptr_dtor(&zv_command);
		zval_ptr_dtor(&zv_ignore_upsert_status);
		RETVAL_BOOL_CHECK_TYPE(retval, "simpleCommandHandleResponse");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.simple_command_handle_response(conn, ok_packet, silent, command, ignore_upsert_status TSRMLS_CC);
	}

	return ret;
}
/* }}} */


MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_FUNC_STATUS(restart_psession, restartpsession, restartPSession, MYSQLND * conn)
MYSQLND_CONN_METHOD_ONE_ARG_CONN_RET_FUNC_STATUS(send_close, sendclose, sendClose, MYSQLND * conn)


/* {{{ */
void MYSQLND_METHOD(mysqlnd_uh_conn, ssl_set)(MYSQLND * const conn, const char * key, const char * const cert, const char * const ca, const char * const capath, const char * const cipher TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		ARG_STRING(key);
		ARG_STRING(cert);
		ARG_STRING(ca);
		ARG_STRING(capath);
		ARG_STRING(cipher)
		zval *retval;
		mysqlnd_uh_call_method_with_6_params(conn_obj, NULL, "sslset", &retval, conn_resource, zv_key, zv_cert, zv_ca, zv_capath, zv_cipher);
		zval_ptr_dtor(&zv_key);
		zval_ptr_dtor(&zv_cert);
		zval_ptr_dtor(&zv_ca);
		zval_ptr_dtor(&zv_capath);
		zval_ptr_dtor(&zv_cipher);
		/* FIXME: mysqli should return boolean but mysqlnd doesn't. bug? */
/*		RETVAL_BOOL_CHECK_TYPE(retval, "sslSet");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret); */
		if (retval)
			zval_ptr_dtor(&retval);
	} else {
		org_mysqlnd_conn_methods.ssl_set(conn, key, cert, ca, capath, cipher TSRMLS_CC);
	}

}
/* }}} */

/* {{{ */
/*
	Plugin related utilities --

	mysqlnd plugins must free their connection data using the mysqlnd methods
	end_psession() and free_contents(), if they associate plugin data with established
	mysqlnd connections. end_psession() is for persistent connections and
	free_contents() is for non-persistent connections. Take good care about
	freeing your persistent or non-persistent data parts accordingly!
*/
static void mysqlnd_uh_conn_free_plugin_connection_data(MYSQLND *conn, zend_bool handler_only TSRMLS_DC) {
	MYSQLND_UH_CONN_DATA **conn_data;

	if (!MYSQLND_UH_G(enabled))
		return;

	conn_data = (MYSQLND_UH_CONN_DATA **) mysqlnd_plugin_get_plugin_connection_data(conn, mysqlnd_uh_plugin_id);
	if (conn_data && *conn_data) {
		/* free user handler object */
		if ((*conn_data)->user_obj) {
			zval_ptr_dtor(&(*conn_data)->user_obj);
			(*conn_data)->user_obj = NULL;
		}

		if (!handler_only) {
			/* do not only reset user handler but free everything */
			if ((*conn_data)->conn_resource) {
				zval_ptr_dtor(&((*conn_data)->conn_resource));
				(*conn_data)->conn_resource = NULL;
			}
			if ((*conn_data)->userland_conn_id) {
				/* TODO
				mnd_pefree((*conn_data)->userland_conn_id, conn->persistent);
				(*conn_data)->userland_conn_id = NULL;
				*/
			}

			mnd_pefree(*conn_data, conn->persistent);
			*conn_data = NULL;
		}
	}
}
/* }}} */

/* {{{ */
enum_func_status MYSQLND_METHOD(mysqlnd_uh_conn, end_psession)(MYSQLND *conn TSRMLS_DC) {
	enum_func_status ret = FAIL;

	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval;
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "endpsession", &retval, conn_resource);
		RETVAL_BOOL_CHECK_TYPE(retval, "endPSession");
		RETVAL_BOOL_TO_FUNC_STATUS(retval, ret);
	} else {
		ret = org_mysqlnd_conn_methods.end_psession(conn TSRMLS_CC);
	}

	mysqlnd_uh_conn_free_plugin_connection_data(conn, FALSE TSRMLS_CC);

	return ret;
}
/* }}} */

/* {{{ */
void MYSQLND_METHOD(mysqlnd_uh_conn, free_contents)(MYSQLND *conn TSRMLS_DC) {
/*
	EXTRACT_DATA_AND_USER_OBJ(conn, conn_data, conn_obj);
	if (conn_obj) {
		ARG_CONN_RESOURCE(conn_resource, conn_data);
		zval *retval;
		mysqlnd_uh_call_method_with_1_params(conn_obj, NULL, "freeContents", &retval, conn_resource);
		if (retval)
			zval_ptr_dtor(&retval);
	} else {
*/
		org_mysqlnd_conn_methods.free_contents(conn TSRMLS_CC);
/*
	}
*/
	mysqlnd_uh_conn_free_plugin_connection_data(conn, FALSE TSRMLS_CC);
}
/* }}} */

/* {{{ */
MYSQLND_RES * MYSQLND_METHOD(mysqlnd_uh_conn, result_init)(unsigned int field_count, zend_bool persistent TSRMLS_DC) {
	MYSQLND_RES* res = NULL;

	res = org_mysqlnd_conn_methods.result_init(field_count, persistent TSRMLS_CC);
	
	return res;
}
/* }}} */

/*
	PHP userland utility functions not directly related to mysqlnd plugins as such
*/

/* {{{ proto bool mysqlnd_uh_set_connection_proxy(object MySQLndUhConnection[, resource mysqli_connection]) */
PHP_FUNCTION(mysqlnd_uh_set_connection_proxy)
{
	zval *mysqlnd_uh_obj_connection;
	zval *mysqli_link = NULL;
	MY_MYSQL *mysql;
	MYSQLND_UH_CONN_DATA **conn_data;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|O",
														&mysqlnd_uh_obj_connection, php_mysqlnd_uh_class_connection_entry,
														&mysqli_link, mysqli_link_class_entry) == FAILURE) {
		RETURN_NULL();
	}

	/* TODO: this should be done at a more appropriate place, m4 or MINIT or the like... */
#if !defined(MYSQLI_USE_MYSQLND)
	php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " You must be using mysqli with mysqlnd. This extension is a mysqlnd plugin.");
	RETURN_FALSE;
#endif

	if (!MYSQLND_UH_G(enabled)) {
		RETURN_FALSE;
	}

	if (!mysqli_link) {
		/* global default user connection object handler not restricted to a particular mysqli connection */

		if (MYSQLND_UH_G(conn_user_obj)) {
			/* free previously set handler, if any */
			zval_ptr_dtor(&(MYSQLND_UH_G(conn_user_obj)));
			MYSQLND_UH_G(conn_user_obj) = NULL;
		}

		Z_ADDREF_P(mysqlnd_uh_obj_connection);
		MYSQLND_UH_G(conn_user_obj) = mysqlnd_uh_obj_connection;

	} else {
		/* specific user handler for a certain mysqli connection */

		/* We need to unwrap the mysqlnd connection that's hidden in the mysqli handle */
		MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL*, &mysqli_link, "mysqli_link", MYSQLI_STATUS_INITIALIZED);
		MYSQLND *conn = mysql->mysqlnd;

		/* free previously set handler, if any */
		mysqlnd_uh_conn_free_plugin_connection_data(conn, TRUE TSRMLS_CC);

		/* (re-)set plugin connection data */
		conn_data = mysqlnd_uh_get_plugin_connection_data(conn, TRUE TSRMLS_CC);
		Z_ADDREF_P(mysqlnd_uh_obj_connection);
		(*conn_data)->user_obj = mysqlnd_uh_obj_connection;
	}

	RETURN_TRUE;
}
/* }}} */


/* {{{ proto resource mysqlnd mysqlnd_uh_convert_to_mysqlnd(mixed connectio_handle) */
PHP_FUNCTION(mysqlnd_uh_convert_to_mysqlnd)
{
	zval *mysqli_link = NULL;
	MY_MYSQL *mysql;
	MYSQLND_UH_CONN_DATA **conn_data;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O",
			&mysqli_link, mysqli_link_class_entry) == FAILURE) {
		RETURN_NULL();
	}

	/* TODO: this should be done at a more appropriate place, m4 or MINIT or the like... */
#if !defined(MYSQLI_USE_MYSQLND)
	php_error_docref(NULL TSRMLS_CC, E_WARNING, MYSQLND_UH_ERROR_PREFIX " You must be using mysqli with mysqlnd. This extension is a mysqlnd plugin.");
	RETURN_FALSE;
#endif

	/* We need to unwrap the mysqlnd connection that's hidden in the mysqli handle */
	MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL*, &mysqli_link, "mysqli_link", MYSQLI_STATUS_INITIALIZED);
	MYSQLND *conn = mysql->mysqlnd;

	ZEND_REGISTER_RESOURCE(return_value, (void *)conn, le_mysqlnd_uh_mysqlnd);
}
/* }}} */


/* {{{ */
/*
	Registration of a mysqlnd plugin and setting hooks:
		a) obtain unique plugin id
		b) get mysqlnd function lookup table for connection, statement,
		   resultset, protocol or net stuff
		c) copy, modify and install new function lookup table
*/
void mysqlnd_uh_minit_register_hooks(TSRMLS_D)
{
	/* It is handy to allow users to disabled plugins via INI_SYSTEM */
	if (!MYSQLND_UH_G(enabled))
		return;

	/* Register as a plugin - mysqlnd.h */
	mysqlnd_uh_plugin_id = mysqlnd_plugin_register();

	/* Get pointer to connection methods */
	my_mysqlnd_conn_methods = mysqlnd_conn_get_methods();

	/* Backup originial function pointers in org_mysqlnd_conn_methods */
	memcpy(&org_mysqlnd_conn_methods, my_mysqlnd_conn_methods, sizeof(struct st_mysqlnd_conn_methods));

	/* Replace selected hooks */
	my_mysqlnd_conn_methods->query							= MYSQLND_METHOD(mysqlnd_uh_conn, query);
	my_mysqlnd_conn_methods->init							= MYSQLND_METHOD(mysqlnd_uh_conn, init);
	my_mysqlnd_conn_methods->connect						= MYSQLND_METHOD(mysqlnd_uh_conn, connect);
	my_mysqlnd_conn_methods->escape_string					= MYSQLND_METHOD(mysqlnd_uh_conn, escape_string);
	my_mysqlnd_conn_methods->set_charset					= MYSQLND_METHOD(mysqlnd_uh_conn, set_charset);
	my_mysqlnd_conn_methods->send_query						= MYSQLND_METHOD(mysqlnd_uh_conn, send_query);
	my_mysqlnd_conn_methods->reap_query						= MYSQLND_METHOD(mysqlnd_uh_conn, reap_query);
	my_mysqlnd_conn_methods->use_result						= MYSQLND_METHOD(mysqlnd_uh_conn, use_result);
	my_mysqlnd_conn_methods->store_result					= MYSQLND_METHOD(mysqlnd_uh_conn, store_result);
	my_mysqlnd_conn_methods->next_result					= MYSQLND_METHOD(mysqlnd_uh_conn, next_result);
	my_mysqlnd_conn_methods->more_results					= MYSQLND_METHOD(mysqlnd_uh_conn, more_results);
	my_mysqlnd_conn_methods->stmt_init						= MYSQLND_METHOD(mysqlnd_uh_conn, stmt_init);
	my_mysqlnd_conn_methods->shutdown_server				= MYSQLND_METHOD(mysqlnd_uh_conn, shutdown_server);
	my_mysqlnd_conn_methods->refresh_server					= MYSQLND_METHOD(mysqlnd_uh_conn, refresh_server);
	my_mysqlnd_conn_methods->ping							= MYSQLND_METHOD(mysqlnd_uh_conn, ping);
	my_mysqlnd_conn_methods->kill_connection				= MYSQLND_METHOD(mysqlnd_uh_conn, kill_connection);
	my_mysqlnd_conn_methods->select_db						= MYSQLND_METHOD(mysqlnd_uh_conn, select_db);
	my_mysqlnd_conn_methods->server_dump_debug_information	= MYSQLND_METHOD(mysqlnd_uh_conn, server_dump_debug_information);
	my_mysqlnd_conn_methods->change_user					= MYSQLND_METHOD(mysqlnd_uh_conn, change_user);
	my_mysqlnd_conn_methods->get_error_no					= MYSQLND_METHOD(mysqlnd_uh_conn, get_error_no);
	my_mysqlnd_conn_methods->get_error_str					= MYSQLND_METHOD(mysqlnd_uh_conn, get_error_str);
	my_mysqlnd_conn_methods->get_sqlstate					= MYSQLND_METHOD(mysqlnd_uh_conn, get_sqlstate);
	my_mysqlnd_conn_methods->get_thread_id					= MYSQLND_METHOD(mysqlnd_uh_conn, get_thread_id);
	my_mysqlnd_conn_methods->get_statistics					= MYSQLND_METHOD(mysqlnd_uh_conn, get_statistics);
	my_mysqlnd_conn_methods->get_server_version				= MYSQLND_METHOD(mysqlnd_uh_conn, get_server_version);
	my_mysqlnd_conn_methods->get_server_information			= MYSQLND_METHOD(mysqlnd_uh_conn, get_server_information);
	my_mysqlnd_conn_methods->get_server_statistics			= MYSQLND_METHOD(mysqlnd_uh_conn, get_server_statistics);
	my_mysqlnd_conn_methods->get_host_information			= MYSQLND_METHOD(mysqlnd_uh_conn, get_host_information);
	my_mysqlnd_conn_methods->get_protocol_information		= MYSQLND_METHOD(mysqlnd_uh_conn, get_protocol_information);
	my_mysqlnd_conn_methods->get_last_message				= MYSQLND_METHOD(mysqlnd_uh_conn, get_last_message);
	my_mysqlnd_conn_methods->charset_name					= MYSQLND_METHOD(mysqlnd_uh_conn, charset_name);
	my_mysqlnd_conn_methods->list_fields					= MYSQLND_METHOD(mysqlnd_uh_conn, list_fields);
	my_mysqlnd_conn_methods->list_method					= MYSQLND_METHOD(mysqlnd_uh_conn, list_method);
	my_mysqlnd_conn_methods->get_last_insert_id				= MYSQLND_METHOD(mysqlnd_uh_conn, get_last_insert_id);
	my_mysqlnd_conn_methods->get_affected_rows				= MYSQLND_METHOD(mysqlnd_uh_conn, get_affected_rows);
	my_mysqlnd_conn_methods->get_warning_count				= MYSQLND_METHOD(mysqlnd_uh_conn, get_warning_count);
	my_mysqlnd_conn_methods->get_field_count				= MYSQLND_METHOD(mysqlnd_uh_conn, get_field_count);
	my_mysqlnd_conn_methods->set_server_option				= MYSQLND_METHOD(mysqlnd_uh_conn, set_server_option);
	my_mysqlnd_conn_methods->set_client_option				= MYSQLND_METHOD(mysqlnd_uh_conn, set_client_option);
	my_mysqlnd_conn_methods->close							= MYSQLND_METHOD(mysqlnd_uh_conn, close);
	my_mysqlnd_conn_methods->query_read_result_set_header	= MYSQLND_METHOD(mysqlnd_uh_conn, query_read_result_set_header);
	my_mysqlnd_conn_methods->simple_command					= MYSQLND_METHOD(mysqlnd_uh_conn, simple_command);
	my_mysqlnd_conn_methods->simple_command_handle_response	= MYSQLND_METHOD(mysqlnd_uh_conn, simple_command_handle_response);
	my_mysqlnd_conn_methods->restart_psession				= MYSQLND_METHOD(mysqlnd_uh_conn, restart_psession);
	my_mysqlnd_conn_methods->send_close						= MYSQLND_METHOD(mysqlnd_uh_conn, send_close);
	my_mysqlnd_conn_methods->ssl_set						= MYSQLND_METHOD(mysqlnd_uh_conn, ssl_set);
	/*
	end_psession() and free_contents() will be called if a connection gets destroyed.
	We must intercept them to free plugin data associated with the connection!
	*/
	my_mysqlnd_conn_methods->end_psession		= MYSQLND_METHOD(mysqlnd_uh_conn, end_psession);
	my_mysqlnd_conn_methods->free_contents		= MYSQLND_METHOD(mysqlnd_uh_conn, free_contents);
}
/* }}} */


/*
  PHP Infrastructure
*/

/* Function arguments and function list */

ZEND_BEGIN_ARG_INFO_EX(mysqlnd_uh_set_connection_proxy_arginfo, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 1)
	ZEND_ARG_OBJ_INFO(1, "MySQLndUhConnection object", MySQLndUhConnection, 0)
	ZEND_ARG_OBJ_INFO(1, "mysqli link", mysqli, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(mysqlnd_uh_convert_to_mysqlnd_arginfo, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 1)
	ZEND_ARG_OBJ_INFO(1, "ext/mysql, ext/mysqli or PDO connection handle", mysqli, 0)
ZEND_END_ARG_INFO()

const zend_function_entry mysqlnd_uh_functions[] = {
	PHP_FE(mysqlnd_uh_set_connection_proxy, mysqlnd_uh_set_connection_proxy_arginfo)
	PHP_FE(mysqlnd_uh_convert_to_mysqlnd, mysqlnd_uh_convert_to_mysqlnd_arginfo)
	{NULL, NULL, NULL}	/* Must be the last line in mysqlnd_uh_functions[] */
};


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
